/*------------------------------------------------------------------------------
 * MDK Middleware - Component ::Network
 * Copyright (c) 2004-2024 Arm Limited (or its affiliates). All rights reserved.
 *------------------------------------------------------------------------------
 * Name:    net_sys.c
 * Purpose: System Module
 *----------------------------------------------------------------------------*/

#include <string.h>
#include "net_lib.h"
#include "net_mem.h"
#include "net_common.h"
#include "net_ip4.h"
#ifdef Network_IPv6
#include "net_ip6.h"
#endif
#include "net_version.h"
#include "net_sys.h"
#include "net_dbg.h"

/* Global variables */
NET_SYS_CTRL net_sys_control;
#ifdef Network_Debug_EVR
 uint32_t net_dbg_buf[9];
#endif

/* Code shortening macros */
#define fn_set  (&netif_setopt_func)
#define fn_get  (&netif_getopt_func)

/* Local variables */
static struct {
  NETOS_ID mutex;
  NETOS_ID thread;
  NETOS_ID timer;
} os_id;

/* Local functions */
static void sys_proc_tick (void);
static NET_IF_CFG *sys_map_if (uint32_t if_id, NET_IF_CFG *const *if_list);
#ifdef Network_Debug_STDIO
 static const char *opt_ascii (netIF_Option opt);
#endif

/**
  \brief       Initialize Network component.
  \return      status code as defined with netStatus.
*/
netStatus netInitialize (void) {
  const net_sys_fn_t *fn_init;

  /* Init OS layer */
  netos_init ();

  /* Create sys protection mutex */
  os_id.mutex = netos_mutex_create (0);
  net_sys_lock ();

#ifdef Network_Debug_EVR
  net_evr_init ();
#endif
  DEBUGF (SYS,"Init Network %s\n",net_ver_ascii);
  EvrNetSYS_InitSystem (net_lib_version);

  net_mem_init ();
  memset (sys, 0, sizeof (*sys));

  sys->RndState = net_lib_version;
  sys->HostName = sysc->HostName;
  sys->Ticks    = 1;

  /* Init protocols and network interfaces */
  for (fn_init = sysc->fn_init; *fn_init != NULL; fn_init++) {
    /* Call "fn_init()" functions from the table */
    (*fn_init)();
  }

  os_id.thread = netos_thread_create ();
  if (os_id.thread == NULL) {
    ERRORF (SYS,"Init, Thread create failed\n");
    EvrNetSYS_ThreadCreateFailed ();
    net_sys_error (NET_ERROR_CONFIG);
  }

  os_id.timer = netos_timer_create ();
  if (os_id.timer == NULL) {
    ERRORF (SYS,"Init, Timer create failed\n");
    EvrNetSYS_TimerCreateFailed ();
    net_sys_error (NET_ERROR_CONFIG);
  }

  DEBUGF (SYS,"Init complete\n");
  EvrNetSYS_InitComplete ();
  net_sys_unlock ();
  return (netOK);
}

/**
  \brief       De-initialize Network component.
  \return      status code as defined with netStatus.
*/
netStatus netUninitialize (void) {
  const net_sys_fn_t *fn_uninit;

  net_sys_lock ();

  DEBUGF (SYS,"Uninit system\n");
  EvrNetSYS_UninitSystem ();

  /* Uninit network protocols and interfaces */
  for (fn_uninit = net_sys_fn_uninit; *fn_uninit != NULL; fn_uninit++) {
    /* Call "fn_uninit()" functions from the table */
    (*fn_uninit)();
  }
  net_sys_unlock ();

  /* Destroy system OS objects */
  netos_timer_delete (os_id.timer);
  netos_thread_delete (os_id.thread);
  netos_mutex_delete (os_id.mutex);
  os_id.thread = NULL;
  os_id.mutex  = NULL;
  os_id.timer  = NULL;

  net_mem_uninit ();
  memset (sys, 0, sizeof (*sys));

  DEBUGF (SYS,"Uninit complete\n");
  EvrNetSYS_UninitComplete ();
  return (netOK);
}

/**
  \brief       Network Core thread.
  \param[in]   arg  dummy parameter.
  \note        Workaround for MW-USB 6.7.0 or older is implemented.
  -------------------------------------------------------------------------------
  The workaround is required for the applications that use network file system
  services (FTP, TFTP,...) which store files on USB host mass storage device.
  The workaround prevents generating wake-up signal during execution of the
  Network Core superloop and clears the signal, if it is already set. The signal
  is also used in USB host when the function fwrite() is blocked to wake up the
  calling thread. If this signal is not generated by USB Host core, waiting
  will prematurely end and the application will crash.
*/
__NO_RETURN void netCore_Thread (void *arg) {
  const net_sys_fn_t *fn_run;
  (void)arg;

  /* Wait until netInitialize complete and start tick timer */
  /* (prevents preemptive thread switch problem)            */
  net_sys_lock ();
  netos_timer_start (os_id.timer, SYS_TICK_INTERVAL);
  net_sys_unlock ();

  while (1) {
    netos_flag_wait (0x0001, NETOS_WAIT_FOREVER);
    while (1) {
      sys_proc_tick ();
      /* Clear signal for USB Host workaround */
      netos_flag_clear (os_id.thread, 0x0001);
      net_sys_lock ();
      /* Run network protocols and interfaces */
      for (fn_run = sysc->fn_run; *fn_run != NULL; fn_run++) {
        /* Call "fn_run()" functions from the table */
        (*fn_run)();
      }
      net_sys_unlock ();
      sys->Flags = 0x00;
      if (!sys->Busy) {
        /* Wait for next wakeup event */
        break;
      }
      netos_thread_pass ();
    }
  }
}

/**
  \brief       Process timer tick event.
*/
static void sys_proc_tick (void) {
  if (sys->Tick) {
    /* Update system timer sync flags */
    sys->Tick  = false;
    sys->Busy  = true;
    sys->Flags = SYS_FLAG_TICK | SYS_FLAG_RUNNING;
    switch (++sys->Time & 0x0F) {
      case 8:
        /* Interval 1 sec, phase shifted */
        sys->Flags |= SYS_FLAG_SEC2;
        if ((sys->Time & 0x3F0) == 0) {
          /* Interval 64 sec, approx. 1 minute */
          sys->Flags |= SYS_FLAG_MIN64;
        }
        break;
      case 10:
        /* Interval 1 sec */
        sys->Time  += 6;
        sys->Flags |= SYS_FLAG_SEC;
        /* Update random generator state */
        net_rand32 ();
        break;
    }
    return;
  }
  sys->Flags = SYS_FLAG_RUNNING;
  sys->Busy  = false;
}

/**
  \brief       System tick timer callback function.
  \param[in]   arg  dummy parameter.
*/
void net_sys_tick (void *arg) {
  (void)arg;
  sys->Tick = true;
  sys->Ticks++;
  net_sys_wakeup ();
}

/**
  \brief       Get current value of an Interface option.
  \param[in]   if_id    interface identifier.
  \param[in]   option   option as specified by netIF_Option.
  \param[out]  buf      buffer to store the option value to
  \param[in]   buf_len  length of buffer
  \return      status code as defined with netStatus.
*/
netStatus netIF_GetOption (uint32_t if_id, netIF_Option option,
                                           uint8_t *buf, uint32_t buf_len) {
  NET_IF_CFG *net_if = net_if_map_all (if_id);

  START_LOCK (netStatus);

  if (net_if == NULL) {
    RETURN (netInvalidParameter);
  }
  DEBUGF (SYS,"GetOption %s, %s\n",net_if->Name,opt_ascii(option));
  EvrNetSYS_GetOption (net_if->Id, option);

  switch (if_id & 0xFF00) {
    case NET_IF_CLASS_ETH:
      if (!fn_get->eth_get_option)  break;
      RETURN (fn_get->eth_get_option (if_id & 0xFF, option, buf, buf_len));

    case NET_IF_CLASS_WIFI:
      if (!fn_get->wifi_get_option) break;
      RETURN (fn_get->wifi_get_option (if_id & 0xFF, option, buf, buf_len));

    case NET_IF_CLASS_PPP:
      if (!fn_get->ppp_get_option)  break;
      RETURN (fn_get->ppp_get_option (option, buf, buf_len));

    case NET_IF_CLASS_SLIP:
      if (!fn_get->slip_get_option) break;
      RETURN (fn_get->slip_get_option (option, buf, buf_len));

    default:
      break;
  }
  RETURN (netInvalidParameter);

  END_LOCK;
}

/**
  \brief       Set the value of an Interface option.
  \param[in]   if_id    interface identifier.
  \param[in]   option   option as specified by netIF_Option.
  \param[in]   buf      buffer containing the option value
  \param[in]   buf_len  length of buffer
  \return      status code as defined with netStatus.
*/
netStatus netIF_SetOption (uint32_t if_id, netIF_Option option,
                                           const uint8_t *buf, uint32_t buf_len) {
  NET_IF_CFG *net_if = net_if_map_all (if_id);

  START_LOCK (netStatus);

  if (net_if == NULL) {
    RETURN (netInvalidParameter);
  }
  DEBUGF (SYS,"SetOption %s, %s\n",net_if->Name,opt_ascii(option));
  EvrNetSYS_SetOption (net_if->Id, option);

  switch (net_if->Id & 0xFF00) {
    case NET_IF_CLASS_ETH:
      if (!fn_set->eth_set_option)  break;
      RETURN (fn_set->eth_set_option (net_if->Id & 0xFF, option, buf, buf_len));

    case NET_IF_CLASS_WIFI:
      if (!fn_set->wifi_set_option) break;
      RETURN (fn_set->wifi_set_option (net_if->Id & 0xFF, option, buf, buf_len));

    case NET_IF_CLASS_PPP:
      if (!fn_set->ppp_set_option)  break;
      RETURN (fn_set->ppp_set_option (option, buf, buf_len));

    case NET_IF_CLASS_SLIP:
      if (!fn_set->slip_set_option) break;
      RETURN (fn_set->slip_set_option (option, buf, buf_len));

    default:
      break;
  }
  RETURN (netInvalidParameter);

  END_LOCK;
}

/**
  \brief       Set default network interface for internet access.
  \param[in]   if_id       interface identifier.
  \param[in]   ip_version  IP version as specified by netIF_Version.
  \return      status code as defined with netStatus.
*/
netStatus netIF_SetDefault (uint32_t if_id, netIF_Version ip_version) {
  NET_IF_CFG *net_if = net_if_map_all (if_id);

  START_LOCK (netStatus);

  if (net_if == NULL) {
    RETURN (netInvalidParameter);
  }
  DEBUGF (SYS,"SetDefault %s, %s\n",net_if->Name,
               (ip_version==netIF_VersionIP4)?"IP4":"IP6");
  EvrNetSYS_SetDefault (net_if->Id, ip_version);

  switch ((int32_t)ip_version) {
    case netIF_VersionIP4:
      /* Default interface for IPv4 */
      if (!net_ip4_set_netif (net_if)) break;
      RETURN (netOK);

#ifdef Network_IPv6
    case netIF_VersionIP6:
      /* Default interface for IPv6 */
      if (!net_ip6_set_netif (net_if)) break;
      RETURN (netOK);
#endif

    default:
      break;
  }
  RETURN (netInvalidParameter);

  END_LOCK;
}

/**
  \brief       Retrieve local host name.
  \return      pointer to local host name.
  \note        This function is used by mbedTLS to detect, if the Network
               component is initialized (returns non null pointer).
*/
const char *netSYS_GetHostName (void) {
  return (sys->HostName);
}

/**
  \brief       Set local host name.
  \param[in]   hostname  new local host name.
  \return      status code as defined with netStatus.
*/
netStatus netSYS_SetHostName (const char *hostname) {
  static char name_buf[NET_HOSTNAME_LEN];

  START_LOCK (netStatus);

  if ((hostname == NULL) || strlen(hostname) >= NET_HOSTNAME_LEN) {
    RETURN (netInvalidParameter);
  }
  DEBUGF (SYS,"SetHostName \"%s\"\n",hostname);
  EvrNetSYS_SetHostName (hostname, strlen(hostname));
  sys->HostName = name_buf;
  net_strcpy (name_buf, hostname);
  RETURN (netOK);

  END_LOCK;
}

/**
  \brief       Get a 32-bit random number.
  \return      random number.
  \note        This is a simple LCG (Linear Congruential Generator)
               pseudo-number generator using ANSI suggested parameters.
*/
uint32_t net_rand32 (void) {
  sys->RndState *= 1103515245;
  sys->RndState += 12345;
  return (sys->RndState);
}

/**
  \brief       Make absolute path for root-folder enabled services.
  \param[in]   root   root folder name.
  \param[in]   fname  file name.
  \return      pointer to a file path.
*/
const char *net_path (const char *root, const char *fname) {
  char *path = sysc->Path;

  if (root && path) {
    int32_t len = net_strcpy (path, root);
    if (fname[0] != '/') {
      path[len++] = '/';
    }
    /* Limited copying of a string to prevent buffer overflow */
    net_strncpy (path+len, fname, (uint32_t)(NET_ROOT_PATH_SIZE-len-1));
    return (path);
  }
  return (fname);
}

/**
  \brief       Get interface descriptor for the specified id.
  \param[in]   if_id    interface identifier.
  \param[in]   if_list  list of interface descriptors.
  \return      pointer to interface descriptor object.
*/
static NET_IF_CFG *sys_map_if (uint32_t if_id, NET_IF_CFG *const *if_list) {
  NET_IF_CFG *const *p;

  for (p = if_list; *p; p++) {
    if ((uint32_t)(*p)->Id == if_id) {
      return (*p);
    }
  }
  return (NULL);
}

/**
  \brief       Map interface id to LAN interface.
  \param[in]   if_id  interface identifier.
  \return      pointer to LAN interface descriptor.
*/
NET_IF_CFG *net_if_map_lan (uint32_t if_id) {
  return (sys_map_if (if_id, net_if_list_lan));
}

/**
  \brief       Map interface id to external interface.
  \param[in]   if_id  interface identifier.
  \return      pointer to interface descriptor.
*/
NET_IF_CFG *net_if_map_all (uint32_t if_id) {
  return (sys_map_if (if_id, net_if_list_all));
}

/**
  \brief       Suspend running user thread.
  \param[out]  thread  pointer to where to store thread id.
*/
void net_sys_suspend (NETOS_ID *thread) {
  *thread = netos_thread_id ();
  do {
    /* net_sys_resume() clears also thread id */
    net_sys_unlock ();
    netos_flag_wait (0x8000, NETOS_WAIT_FOREVER);
    net_sys_lock ();
  } while (*thread);
}

/**
  \brief       Resume suspended user thread in blocking mode.
  \param[in]   thread  pointer to a suspended thread id.
*/
void net_sys_resume (NETOS_ID *thread) {
  if (*thread) {
    NETOS_ID th = *thread;
    *thread     = NULL;
    netos_flag_set (th, 0x8000);
  }
}

/**
  \brief       Suspend running user thread.
  \param[out]  ms   timeout to wait for flag (in ms).
*/
void net_sys_wait (uint32_t ms) {
  net_sys_unlock ();
  netos_flag_wait (0x8000, ms);
  net_sys_lock ();
}

/**
  \brief       Lock network core functions.
*/
void net_sys_lock (void) {
  netos_lock (os_id.mutex);
}

/**
  \brief       Unlock network core functions.
*/
void net_sys_unlock (void) {
  netos_unlock (os_id.mutex);
}

/**
  \brief       Wake-up network thread.
*/
void net_sys_wakeup (void) {
  if (sys->Flags & SYS_FLAG_RUNNING) {
    /* Network core currently running */
    sys->Busy = true;
    return;
  }
  netos_flag_set (os_id.thread, 0x0001);
}

#ifdef Network_Debug_STDIO
/**
  \brief       Print time stamp in debug mode.
  \return      pointer to null-terminated ascii string.
*/
const char *net_debug_time (void) {
  static uint32_t time_old = 0x10;
  static char dbg_ts[12];
  int32_t n;

  if (sys->Time != time_old) {
    time_old = sys->Time;
    /* Format: "%03d.%1d " */
    n  = net_itoa_n0 (dbg_ts, sys->Time>>4, 3);
    dbg_ts[n++] = '.';
    n += net_itoa (dbg_ts+n, sys->Time&0x0F);
    dbg_ts[n++] = ' ';
    dbg_ts[n]   = 0;
  }
  return (dbg_ts);
}

/**
  \brief       Convert option code to ascii.
  \param[in]   opt  option code.
  \return      pointer to text representation of an option.
*/
static const char *opt_ascii (netIF_Option opt) {
  switch ((int32_t)opt) {
    case netIF_OptionMAC_Address:
      return ("MAC-Addr");
    case netIF_OptionVLAN_Identifier:
      return ("VLAN-ID");
    case netIF_OptionIP4_MTU:
      return ("IP4-MTU");
    case netIF_OptionIP4_Address:
      return ("IP4-Addr");
    case netIF_OptionIP4_SubnetMask:
      return ("IP4-Mask");
    case netIF_OptionIP4_DefaultGateway:
      return ("IP4-DefGW");
    case netIF_OptionIP4_PrimaryDNS:
      return ("IP4-PriDNS");
    case netIF_OptionIP4_SecondaryDNS:
      return ("IP4-SecDNS");
#ifdef Network_IPv6
    case netIF_OptionIP6_MTU:
      return ("IP6-MTU");
    case netIF_OptionIP6_LinkLocalAddress:
      return ("IP6-LLAddr");
    case netIF_OptionIP6_StaticAddress:
      return ("IP6-StatAddr");
    case netIF_OptionIP6_DynamicAddress:
      return ("IP6-DynAddr");
    case netIF_OptionIP6_SubnetPrefixLength:
      return ("IP6-PrefLen");
    case netIF_OptionIP6_DefaultGateway:
      return ("IP6-DefGW");
    case netIF_OptionIP6_PrimaryDNS:
      return ("IP6-PriDNS");
    case netIF_OptionIP6_SecondaryDNS:
      return ("IP6-SecDNS");
#endif
    default:
      return ("Unknown");
  }
}

/**
  \brief       Convert network process ID to ascii string.
  \param[in]   proc  process id number.
  \return      pointer to null-terminated ascii string.
  \note        Must be in sync with "net_lib.h" definitions!!
*/
const char *net_debug_proc (int32_t proc) {
  static const char proc_id[31][6] = {
    "SYS",  "MEM",
    "ETH",  "WIFI", "PPP",  "SLIP", "LOOP",
    "ARP",  "IP4",  "ICMP", "IGMP",
    "IP6",  "ICMP6","NDP",  "MLD",
    "UDP",  "TCP",  "BSD",
    "NBNS", "DHCP", "DHCP6","DNS",  "SNMP",
    "HTTP", "FTP",  "FTPC", "TELN", "TFTP",
    "TFTPC","SMTP", "SNTP" };
  return (proc_id[proc]);
}
#endif
